//+---------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation, 2008
//
// File: DataPointSchema.cs
//
// Contents: DataPointsSchema class - contract between the calling application and the logger
//           - defines the data accepted by the logger and how to validate it
//
// Created by: Adam Wardynski (AdamWa) 2/4/2008
//
//----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;

namespace Microsoft.Reputation.Services
{
    internal enum DataPointType
    {
        String,
        Int
    }
    /// <summary>
    /// DataPointSchema class acts as the contract between the calling application and the logger
    /// This schema defines the data accepted by the logger and how to validate it
    /// </summary>
    internal class DataPointSchema
    {
        private Dictionary<String, DataPointSchemaElement> m_schema;
        private List<DataPointSchemaElement> m_ordinal;
        private char m_delimiter;
        private string m_xml;

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="schema">Dictionary of name -> DataPointSchemaElement</param>
        /// <param name="ordinal">List of same DataPointSchemaElements in schema variable just in different collection for access purposes</param>
        /// <param name="xml">config xml</param>
        /// <param name="delimiter">delimiter used to seperate data elements</param>
        internal DataPointSchema(Dictionary<String, DataPointSchemaElement> schema, List<DataPointSchemaElement> ordinal, char delimiter, string xml)
        {
            this.m_schema = schema;
            this.m_ordinal = ordinal;
            this.m_delimiter = delimiter;
            this.m_xml = xml.Replace("\n", String.Empty);
        }

        /// <summary>
        /// Creates a string representation of a DataPoint based on the provided schema
        /// NOTE: DataPoint does not have to be valid
        /// </summary>
        /// <param name="data">The DataPoint to write to a string</param>
        /// <returns>all the data in the DataPoint arranged as described in the schema as a string (tab delimited and ending with new line)</returns>
        internal string WriteData(DataPoint data)
        {
            StringBuilder sb = new StringBuilder();
            // iterate through all possible elements in order of ordinal
            foreach (DataPointSchemaElement element in m_ordinal)
            {
                // write each record ending with a tab/delimeter
                object value;
                if (data.TryGetValue(element.Name, out value))
                {
                    sb.Append(value);
                }

                sb.Append(m_delimiter); // delimiter is variable
            }
            // remove trailing \t
            sb.Remove(sb.Length - 1, 1);
            // end sting with carrige return because that is what sql reads correctly
            sb.Append("\r\n");

            return sb.ToString();
        }

        /// <summary>
        /// Validates DataPoint against schema.  Ensures all required elements are in the DataPoint, and each data element is of the appropirate type
        /// </summary>
        /// <param name="data">DataPoint to validate</param>
        /// <returns>true if DataPoint is valid according to the schema otherwise false</returns>
        internal bool IsValid(DataPoint data)
        {
            Object obj;
            foreach (DataPointSchemaElement element in m_schema.Values)
            {
                // make sure required elements exist
                if (!data.TryGetValue(element.Name, out obj))
                {
                    if (element.Required)
                    {
                        return false;
                    }
                }
                else
                {
                    // validate type
                    switch (element.Type)
                    {
                        case DataPointType.Int:
                            if (!(obj is int))
                            {
                                return false;
                            }
                            break;
                        case DataPointType.String:
                            if (!(obj is string))
                            {
                                return false;
                            }
                            break;
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// Read only accessor for xml
        /// </summary>
        internal string Xml
        {
            get
            {
                return m_xml;
            }
        }
    }

    /// <summary>
    /// Internal structure represent one element of a data point
    /// name of the element - must be unique
    /// type of the data - must be "string" or "int"
    /// ordinal - order to write element in log
    /// required - indicates if each data point must contain this element
    /// </summary>
    internal struct DataPointSchemaElement
    {
        public readonly string Name;
        public readonly DataPointType Type;
        public readonly int Ordinal;
        public readonly bool Required;

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="name">name of the element - must be unique</param>
        /// <param name="type">type of the data - must be "string" or "int"</param>
        /// <param name="ordinal">ordinal - order to write element in log - must be greater than 0</param>
        /// <param name="required"></param>
        internal DataPointSchemaElement(string name, DataPointType type, int ordinal, bool required)
        {
            // validate ordinal
            if (ordinal < 0)
            {
                throw new ArgumentException("Ordinal is less than 0", "ordinal");
            }

            this.Name = name;
            this.Type = type;
            this.Ordinal = ordinal;
            this.Required = required;
        }
    }
}